package com.mongo.service.impl;

import com.mongo.annotation.EncryptField;
import com.mongo.entity.BaseEntity;
import com.mongo.enums.EncryptType;
import com.mongo.secret.Des;
import com.mongo.secret.Secret;
import com.mongo.service.MongoMapper;
import com.mongo.util.SecretFactory;
import com.mongo.util.SpringUtils;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.env.Environment;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.util.Assert;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 实现对mongo的增删改查功能
 *
 * @author xiazhun
 * @create 2022/8/7
 */
public class MongoMapperImpl<T> implements MongoMapper<T> {

    private final MongoTemplate mongoTemplate;

    protected AtomicBoolean encrypt = new AtomicBoolean(false);
    private transient Class<T> clazz;

    public void setClazz(Class<T> clazz) {
        this.clazz = clazz;
    }

    public MongoMapperImpl(MongoTemplate mongoTemplate, boolean encrypt) {
        this.mongoTemplate = mongoTemplate;
        this.encrypt.set(encrypt);
    }

    @Override
    public Class<T> getClazz() {
        return this.clazz;
    }

    @Override
    public T queryById(String id) {
        T t = (T) mongoTemplate.findById(id, getClazz());
        return SecretFactory.decode(this.encrypt.get(), t);
    }


    @Override
    public T queryOne(Query query) {
        T t = (T) mongoTemplate.findOne(query, getClazz());
        return SecretFactory.decode(this.encrypt.get(), t);
    }


    @Override
    public T query(LambdaQuery<T> lambdaQuery) {
        return queryOne(lambdaQuery.getQuery());
    }

    @Override
    public List<T> list(LambdaQuery<T> lambdaQuery) {
        return list(lambdaQuery.getQuery());
    }

    @Override
    public List<T> list(Query query) {
        List<T> data = mongoTemplate.find(query, getClazz());
        List<T> list = new ArrayList<>();
        for (T t : data) {
            T res = SecretFactory.decode(this.encrypt.get(), t);
            list.add(res);
        }
        return list;
    }

    @Override
    public T insert(T t) {
        Document document = BaseEntity.getDocument(getClazz());
        if (Objects.nonNull(document)) {
            T data = SecretFactory.encrypt(this.encrypt.get(), t);
            return mongoTemplate.save(data, document.value());
        }
        return null;
    }

    @Override
    public long update(Query query, Update update) {
        Assert.notEmpty(query.getQueryObject(), "条件不能为空");
        Assert.notEmpty(update.getUpdateObject(), "值不能为空");
        if (this.encrypt.get()){
        org.bson.Document updateObject = update.getUpdateObject();
        Field[] filedName = SecretFactory.getFiledName(getClazz());
        for (String key : updateObject.keySet()) {
            if ("$set".equals(key)) {
                org.bson.Document document = (org.bson.Document) updateObject.get(key);
                for (Field field : filedName) {
                    if (null != field) {
                        String name = field.getName();
                        if (document.containsKey(name)) {
                            EncryptField annotation = field.getAnnotation(EncryptField.class);
                            if (null != annotation) {
                                EncryptType type = annotation.type();
                                //获取加密工具
                                Secret secret = SecretFactory.type(type);
                                if (null != secret) {
                                    String publicKey = null;
                                    if (Arrays.asList(EncryptType.SM2, EncryptType.RSA).contains(type)) {
                                        Environment environment = SpringUtils.getBean(Environment.class);
                                        //获取全局私钥
                                        String property = environment.getProperty("mongo.secret.publicKey.path");
                                        if (StringUtils.isNotBlank(property)) {
                                            String path = Des.getInstance().decode(property);
                                            File file = new File(path);
                                            try {
                                                FileReader reader = new FileReader(file);
                                                BufferedReader br = new BufferedReader(reader);
                                                StringBuilder sb = new StringBuilder();
                                                String line;
                                                while ((line = br.readLine()) != null) {
                                                    sb.append(line);
                                                }
                                                publicKey = sb.toString();
                                            } catch (Exception e) {
                                                throw new RuntimeException("找不到公钥文件");
                                            }
                                        }else{
                                            publicKey = annotation.publicKey();
                                        }
                                        Assert.notNull(publicKey, "publicKey must not be null");
                                    } else {
                                        String keys = annotation.key();
                                        if (StringUtils.isNotBlank(keys)) {
                                            publicKey = keys;
                                        }
                                    }
                                    String encrypt;
                                    if (StringUtils.isNotBlank(publicKey)) {
                                        encrypt = secret.encrypt(document.get(name).toString(), publicKey);
                                    } else {
                                        encrypt = secret.encrypt(document.get(name).toString());
                                    }
                                    update.set(name, encrypt);
                                }
                            }
                        }
                    }
                }
            }
        }
        }
        UpdateResult upsert = mongoTemplate.upsert(query, update, getClazz());
        return upsert.getModifiedCount();
    }

    @Override
    public long update(LambdaMongoUpdate<T> lambdaMongoUpdate) {
        long res = 0;
        Query query = lambdaMongoUpdate.getQuery();
        Update update = lambdaMongoUpdate.getUpdate();
        if (query.getQueryObject().isEmpty()) {
            throw new IllegalArgumentException("条件为空");
        }
        //如果update没有设置key,value则不更新,否则会造成数据清空
        if (!update.getUpdateObject().isEmpty()) {
            res = update(query, update);
        }
        return res;
    }

    @Override
    public long remove(Query query) {
        DeleteResult remove = mongoTemplate.remove(query, getClazz());
        return remove.getDeletedCount();
    }

    @Override
    public long removeById(String id) {
        DeleteResult remove = mongoTemplate.remove(new Query(Criteria.where("_id").is(id)),getClazz());
        return remove.getDeletedCount();
    }

    @Override
    public long removeByIds(Collection<String> collection) {
        long count = 0;
        for (String id : collection) {
            count += removeById(id);
        }
        return count;
    }

    @Override
    public PageImpl<T> page(Query query, PageRequest pageRequest) {
        long size = size(query); // 总数
        query.with(pageRequest);
        List<T> list = list(query);
        return new PageImpl<>(list, pageRequest, size);
    }

    @Override
    public long size(Query query) {
        return mongoTemplate.count(query, getClazz());
    }

    @Override
    public MongoTemplate mongoTemplate() {
        return mongoTemplate;
    }
}
